React - 3 高级内容

React高级

propTypes和DefaultProps

propTypes:组件要接受外部传过来的值,进行类型校验。验证传值

DefaultProps:若父组件未向子组件传值,定义属性默认值。设置默认值

https://reactjs.org/docs/typechecking-with-proptypes.html

1
2
3
4
5
6
7
8
9
10
11
12
13
import PropTypes from 'prop-types';

// 组件要接受外部传过来的值,进行类型校验。验证传值
TodoItem.propTypes = {
test: PropTypes.string.isRequired,
content: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
deleteItem: PropTypes.func,
index: PropTypes.number
}
// 若父组件未向子组件传值,定义属性默认值。设置默认值
TodoItem.defaultProps = {
test: '-'
}

props、state、render函数关系

当组件的state或者props发生改变的时候,render函数就会重新执行

当父组件的render函数被运行时,他的子组件的render都被重新运行一次

虚拟DOM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//真实DOM - 全部替换
缺陷:第一次生成DOM片段,第二次生成DOM。第二次DOM地带第一次DOM,非常耗费性能
1.state 数据
2.JSX 模板
3.数据+模板 结合,生成真实的DOM显示
4.state 发生改变
5.数据+模板 结合,生成真实的DOM,替换原始的DOM

//真实DOM - DOM比对后替换
缺陷:性能提升并不明显。替换内容变性能变少,比对性能增加
1.state 数据
2.JSX 模板
3.数据+模板 结合,生成真实的DOM显示
4.state 发生改变
5.数据+模板 结合,生成真实的DOM,不直接替换原始的DOM
6.新的DOM和原始的DOM做比对,找差异
7.找出input框发生改变
8.只用新的DOM中input元素,替换DOM中的input元素

//虚拟DOM - JS比对
// 优点:
//1.性能提升了:减少了对真实DOM的1 创建和2 对比的性能
//2.跨端应用得以实现:React Native,虚拟DOM转化为原生应用的组件。
1.state 数据
2.JSX 模板
3.数据+模板 结合,生成虚拟DOM(虚拟DOM是一个JS对象,用它来描述真实DOM)
['div',{id:'123'},['span',{},'hello world']]//本身+属性+内容
4.用虚拟DOM的结构生成真实的DOM显示
<div id='123'><span>hello world </span></div>
5.state 发生变化
6.数据+模板 结合,生成新的虚拟DOM
['div',{id:'123'},['span',{},'bye bye']]//本身+属性+内容
7.比较原始虚拟DOM和新的虚拟DOM的区别
8.直接操作DOM,改变span中的内容

- 极大提升性能
- 性能损耗极小,JS生成JS对象消耗小
- JS生成DOM对象消耗极大,调用web application的API

流程:JSX —> createElement—>虚拟DOM(JS对象) —> 真实的DOM

1
2
return <div>item</div>
return React.createElement('div',{},'item')

虚拟DOM的diff算法

diff算法:比对原始虚拟DOM和新的虚拟DOM的差异

虚拟DOM时候会被比对 —> 什么时候数据改变 —> 调用setState

1.setState设计成异步函数的原因?

假设连续调用三次setState,且三次调用的时间间隔非常小。react把三次,合并为一次,只做一次虚拟DOM比对,更新一次DOM。节省了两次DOM比对和更新的时间,性能提升。

diff算法:

  • 同级比对:算法简单,比对算法的速度快。同层比较,若有差异,本层及下层重新渲染,
  • key值比对:提高了比对的性能。
    • 需要保证:原虚拟DOM树和新虚拟DOM树上的key值一致。
    • 因此不能使用index做key值,删除之后index会改变,key值不稳定
    • key值要稳定,用item做key值

React中ref的使用

用处:帮助我们直接获取 DOM元素

一般情况:然而并不推荐直接操作DOM元素,因为react是操纵数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{/*构建ref的引用;引用名为:this.input;指向input对应的dom节点*/}
<input
id="insertArea"
className='input'
value={this.state.inputValue}
onChange={this.handleInputChange}
ref={(input)=>{this.input = input}}
/>

// 1.1输入框改变,保存输入框内容
handleInputChange() {
// const value = e.target.value;
const value = this.input.value;
this.setState(() => ({
inputValue: value
}));
}

ref和setState使用需要注意的地方:

DOM的获取并不及时,因为setState是异步的。如果希望页面更新后获取DOM,将获取DOM的语法放在setState第二个参数中,他是一个回调函数。

1
2
3
4
5
6
7
8
9
10
11
// 1.2点击add,增加一项数据
handleBtnClick() {
// setState异步,第一个参数需要执行的函数,第二个参数回调函数
// prevState:修改数据之前的那一次数据的状态;避免不小心改变state状态
this.setState((prevState) => ({
list: [...prevState.list, prevState.inputValue],
inputValue: ''
}),()=>{
console.log(this.ul.querySelectorAll('div').length);
});
}

React生命周期函数

生命周期函数:在某一时刻,组件会自动调用的函数

render:setState

image-20190416215418582

初始化initialization

挂载mounting

更新updation

去除unmounting

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//2组件即将被挂载到页面的时刻执行
componentWillMount(){
console.log('2 组件即将被挂载到页面:componentWillMount')
}
//2组件即将被挂载到页面的时刻执行
componentDidMount(){
console.log('2 组件被挂载到页面之后:componentDidMount')
}
//3组件需要更新吗?true or false
shouldComponentUpdate(){
console.log('3 组件是否更新?:shouldComponentUpdate')
return true;
}
//3组件即将更新
componentWillUpdate(){
console.log('3 组件更新之前:componentWillUpdate')
}
//3组件更新完成
componentDidUpdate(){
console.log('3 组组件更新完成:componentDidUpdate')
}
1
2
3
4
5
6
7
8
9
10
11
// 3(1)当组件从父组件接收参数;(2)父组件的render执行了,该生命周期函数会执行
// 如果这个组件第一次存在于父组件中,不会执行
// 如果这个组件已经存在与父组件中,会执行
componentWillReceiveProps() {
console.log('3 ! 组件即将获取父组件:componentWillReceiveProps')
}

//4 子组件即将被去除
componentWillUnmount(){
console.log('4 组件即将被去除:child componentWillUnmout')
}

React生命周期函数使用场景

性能优化:借助shouldComponentUpdate

原因:提高react组件性能,避免无谓的组件render渲染

1
2
3
4
5
6
7
8
9
10
// 避免一个组件做无谓的render操作
// 利用shouldComponentUpdate提升性能:若更新后的content和更新前的content一致,则不刷新渲染
shouldComponentUpdate(nextProps,nextState){
//nextProps指更新后Props会变成什么样,nextState指的是State会变化成什么样
if(nextProps.content !== this.props.content){
return true;
}else{
return false;
}
}

发送ajax请求:默认放在componentDidMount

原因:ajax只需要请求1次,需要在渲染前获取数据

场景:使用ajax异步请求的方式,从我的一个接口上,请求一个远程的todolist数据,将远程数据加载到本地,渲染到页面上。

1
2
3
4
5
// npm、或者初始化脚本方法安装yarn 
npm install -g yarn
curl -o- -L https://yarnpkg.com/install.sh | bash
// yarn安装axios
yarn add axios
1
2
3
4
// 约定:ajax放在componentDidMount中
// ajax只需要请求1次
componentDidMount(){
}

使用Charles实现本地数据mock

Charles:中间代理服务器,可以抓取浏览器的请求,可以使用map local模拟本地前端接口。

模拟一个接口,ajax请求获取接口中真正的结果,改变页面的内容。

1
2
3
4
5
6
7
8
9
10
11
componentDidMount(){
// axios.get('/api/todolist')
axios.get('http://localhost.charlesproxy...:3000')
.then((res)=>{
this.setSate(()=>({
list: [...res.data]
})
);
})
.catch(()=>{alert('错误')})
}

CSS3的过渡动画和动画

监听state里show的值,根据值在div元素中动态添加css样式className

1
<div className={this.state.show?'show':'hide'}>hello</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*过渡动画*/
.show{
opacity: 1;
transition: all 1s ease-in;
}
/*动画*/
.hide{
animation: hide-item 2s ease-in forwards;
}
@keyframes hide-item{
0%{
opacity: 1;
color: red;
}
50%{
opacity: 0.5;
color: green;
}
100%{
opacity: 0;
color: blue;
}
}

react-transition-group实现动画

文档:https://reactcommunity.org/react-transition-group/css-transition

安装:yarn add react-transition-group

1
2
3
4
//不需要样式的添加和删除
<CSSTransition>
<div>hello world</div>
</CSSTransition>

性能优化

  • 作用域修改放在constructor构造函数内,保证作用域绑定操作只执行一次 + 避免子组件的无谓渲染。

    this.handleClick = this.handleClick.bind(this);

  • react底层setState性能提升机制,使用异步函数:多次数据改变结合成一次来做,降低虚拟DOM频率

  • react底层虚拟DOM,同层比对+使用key值比对,提升虚拟DOM比对速度
  • 借助shouldComponentUpdate,提高react组件性能,避免无谓的组件render渲染